﻿using System;
using ParserGenerator.Collections;
using ParserGenerator.GrammarConstruction;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonAnnotationNode
    {
        public ParserAutomatonState State { get; }
        public ParserAutomatonInadequacyNode InadequacyNode { get; }
        public ParserAutomatonContributionMatrix Contributions { get; }

        internal ParserAutomatonAnnotationNode(ParserAutomatonState state, ParserAutomatonInadequacyNode inadequacyNode, ParserAutomatonContributionMatrix contributions)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (inadequacyNode == null)
                throw new ArgumentNullException(nameof(inadequacyNode));
            if (contributions == null)
                throw new ArgumentNullException(nameof(contributions));
            State = state;
            InadequacyNode = inadequacyNode;
            Contributions = contributions;
        }

        internal bool IsAlwaysContribution(ParserAutomatonContribution contribution)
        {
            if (contribution == null)
                throw new ArgumentNullException(nameof(contribution));
            return Contributions[contribution] == null;
        }

        private bool StateMakesContribution(ParserAutomatonContribution contribution, ParserAutomatonItemLookaheadSets sets)
        {
            if (IsAlwaysContribution(contribution))
                return true;
            else if (sets == null)
                return false;
            else
            {
                GrammarTerminalSymbol token = InadequacyNode.ConflictedToken;
                ReadOnlySet<ParserAutomatonItem> items = Contributions[contribution];
                foreach (ParserAutomatonItem item in items)
                {
                    ParserAutomatonItemLookaheadSet set;
                    if (sets.TryGetValue(item, out set) && set.Contains(token))
                        return true;
                }
                return false;
            }
        }

        internal ParserAutomatonContribution ComputeDominantContribution(ParserAutomatonItemLookaheadSets sets, bool requireSplitStable)
        {
            ParserAutomatonState manifestingState = InadequacyNode.ManifestingState;
            GrammarTerminalSymbol conflictedToken = InadequacyNode.ConflictedToken;
            ParserAutomatonShift shift = InadequacyNode.ShiftContribution;
            if (shift == null)
            {
                foreach (ParserAutomatonContribution reduce in Contributions.Keys)
                    if (StateMakesContribution(reduce, sets))
                        return !requireSplitStable || IsAlwaysContribution(reduce) ? reduce : null;
                return null;
            }
            else
            {
                bool foundStableDominationOverShift = false;
                bool foundStableErrorActionDomination = false;
                ParserAutomatonContribution rrDominator = null;
                int? shiftPrecedence = conflictedToken.Precedence;
                if (shiftPrecedence == null)
                    return shift;
                foreach (ParserAutomatonReduction reduce in manifestingState.Reductions.Values)
                {
                    GrammarProduction prod = reduce.Production;
                    int? reducePrecedence = prod.Precedence;
                    bool chooseReduce = reducePrecedence == null ||
                                        reducePrecedence > shiftPrecedence ||
                                        reducePrecedence == shiftPrecedence &&
                                        conflictedToken.Associativity.Value != OperatorAssociativity.Right;
                    if (chooseReduce && StateMakesContribution(reduce, sets))
                    {
                        if (reducePrecedence == shiftPrecedence && conflictedToken.Associativity.Value == OperatorAssociativity.Nonassociative)
                        {
                            if (foundStableDominationOverShift)
                                break;
                            else if (!requireSplitStable || IsAlwaysContribution(reduce))
                                return null;
                            else
                                foundStableErrorActionDomination = true;
                        }
                        if (rrDominator == null)
                            rrDominator = reduce;
                        if (reducePrecedence != null)
                        {
                            if (foundStableErrorActionDomination)
                                break;
                            else if (!requireSplitStable)
                                return rrDominator;
                            else if (!IsAlwaysContribution(rrDominator))
                                break;
                            else if (IsAlwaysContribution(reduce))
                                return rrDominator;
                            else
                                foundStableDominationOverShift = true;
                        }
                    }
                }
                if (foundStableDominationOverShift || foundStableErrorActionDomination)
                    return null;
                return shift;
            }
        }

        internal int CompareTo(ParserAutomatonAnnotationNode other)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            int t = InadequacyNode.Number.CompareTo(other.InadequacyNode.Number);
            if (t != 0)
                return t;
            foreach (ParserAutomatonContribution contribution in Contributions.Keys)
            {
                if (IsAlwaysContribution(contribution))
                {
                    if (!other.IsAlwaysContribution(contribution))
                        return -1;
                }
                else if (other.IsAlwaysContribution(contribution))
                {
                    return 1;
                }
                else
                {
                    ReadOnlySet<ParserAutomatonItem> xs = Contributions[contribution];
                    ReadOnlySet<ParserAutomatonItem> ys = other.Contributions[contribution];
                    foreach (ParserAutomatonItem item in State.Core)
                    {
                        if (!xs.Contains(item))
                        {
                            if (ys.Contains(item))
                                return -1;
                        }
                        else if (!ys.Contains(item))
                        {
                            return 1;
                        }
                    }
                }
            }
            return 0;
        }
    }
}
